package cn.iocoder.yudao.module.reading.service.punch;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.PunchCalendarRespVO;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.PunchCreateReqVO;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.PunchRecordRespVO;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.UserStatsRespVO;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.PunchUpdateReqVO;
import cn.iocoder.yudao.module.reading.controller.app.punch.vo.PunchCalendarAllRespVO;
import cn.iocoder.yudao.module.reading.convert.punch.PunchConvert;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.reading.dal.mysql.book.PictureBookMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.bookset.BookSetMapper;
import cn.iocoder.yudao.module.reading.dal.dataobject.book.PictureBookDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.bookset.BookSetDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.book.PictureBookDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.bookset.BookSetDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.punch.PunchBookDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.punch.PunchRecordDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.record.ReadingRecordDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.stats.UserReadingStatsDO;
import cn.iocoder.yudao.module.reading.dal.mysql.book.PictureBookMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.bookset.BookSetMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.punch.PunchBookMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.punch.PunchRecordMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.record.ReadingRecordMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.stats.UserReadingStatsMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.user.UserMapper;
import cn.iocoder.yudao.module.reading.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.reading.service.follow.FollowService;
import cn.iocoder.yudao.module.reading.framework.util.StaticResourceUrlUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Resource;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.reading.enums.ErrorCodeConstants.*;

/**
 * 学习打卡 Service 实现类
 *
 * @author 李晨
 */
@Service
@Slf4j
public class PunchServiceImpl implements PunchService {

    @Resource
    private PunchRecordMapper punchRecordMapper;
    @Resource
    private PunchBookMapper punchBookMapper;
    @Resource
    private UserReadingStatsMapper userReadingStatsMapper;
    @Resource
    private ReadingRecordMapper readingRecordMapper;
    @Resource
    private PictureBookMapper pictureBookMapper;
    @Resource
    private BookSetMapper bookSetMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private FollowService followService;
    @Resource
    private StaticResourceUrlUtils staticResourceUrlUtils;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPunch(Long userId, PunchCreateReqVO createReqVO) {
        LocalDate today = LocalDate.now();

        // 1. 检查今日是否已打卡
        PunchRecordDO existingPunch = punchRecordMapper.selectByUserIdAndDate(userId, today);

        // 2. 验证今日是否有阅读记录
        LocalDateTime todayStart = today.atStartOfDay();
        LocalDateTime todayEnd = today.atTime(LocalTime.MAX);
        List<ReadingRecordDO> todayReadings = readingRecordMapper.selectByUserIdAndDateRange(userId, todayStart, todayEnd);

        if (CollUtil.isEmpty(todayReadings)) {
            throw exception(PUNCH_RECORD_NO_READING_TODAY);
        }

        // 3. 获取今日所有阅读的绘本ID（支持重复打卡时包含新阅读的绘本）
        List<Long> todayBookIds = todayReadings.stream()
                .map(ReadingRecordDO::getBookId)
                .distinct()
                .collect(Collectors.toList());

        // 4. 如果今日已打卡，则更新现有记录（重复打卡功能）
        if (existingPunch != null) {
            log.info("用户{}今日已打卡，执行重复打卡更新逻辑", userId);

            // 更新打卡记录内容
            PunchRecordDO updateObj = PunchRecordDO.builder()
                    .id(existingPunch.getId())
                    .commentText(createReqVO.getCommentText())
                    .bookCount(todayBookIds.size())
                    .isPublic(createReqVO.getIsPublic())
                    .build();
            punchRecordMapper.updateById(updateObj);

            // 删除原有的绘本关联
            punchBookMapper.deleteByPunchId(existingPunch.getId());

            // 创建新的绘本关联（包含所有今日阅读的绘本）
            for (Long bookId : todayBookIds) {
                ReadingRecordDO readingRecord = todayReadings.stream()
                        .filter(record -> record.getBookId().equals(bookId))
                        .findFirst()
                        .orElse(null);

                PunchBookDO punchBook = PunchBookDO.builder()
                        .punchId(existingPunch.getId())
                        .bookId(bookId)
                        .readAt(readingRecord != null ? readingRecord.getLastReadAt() : LocalDateTime.now())
                        .build();

                punchBookMapper.insert(punchBook);
            }

            // 更新用户统计数据
            updateUserStats(userId);

            return existingPunch.getId();
        }

        // 5. 首次打卡：验证提交的绘本ID是否在今日阅读记录中
        for (Long bookId : createReqVO.getBookIds()) {
            if (!todayBookIds.contains(bookId)) {
                throw exception(PUNCH_RECORD_NO_READING_TODAY);
            }
        }

        // 6. 计算累计打卡天数
        Long totalDays = punchRecordMapper.countByUserId(userId);

        // 7. 创建新的打卡记录
        PunchRecordDO punchRecord = PunchRecordDO.builder()
                .userId(userId)
                .punchDate(today)
                .commentText(createReqVO.getCommentText())
                .bookCount(createReqVO.getBookIds().size())
                .dayCount(totalDays.intValue() + 1)
                .isPublic(createReqVO.getIsPublic())
                .build();

        punchRecordMapper.insert(punchRecord);

        // 8. 创建打卡绘本关联
        for (Long bookId : createReqVO.getBookIds()) {
            // 找到对应的阅读记录获取阅读时间
            ReadingRecordDO readingRecord = todayReadings.stream()
                    .filter(record -> record.getBookId().equals(bookId))
                    .findFirst()
                    .orElse(null);

            PunchBookDO punchBook = PunchBookDO.builder()
                    .punchId(punchRecord.getId())
                    .bookId(bookId)
                    .readAt(readingRecord != null ? readingRecord.getLastReadAt() : LocalDateTime.now())
                    .build();

            punchBookMapper.insert(punchBook);
        }

        // 9. 更新用户统计数据
        updateUserStats(userId);

        return punchRecord.getId();
    }

    @Override
    public PunchRecordDO getPunchRecord(Long id) {
        PunchRecordDO punchRecord = punchRecordMapper.selectById(id);
        if (punchRecord == null) {
            throw exception(PUNCH_RECORD_NOT_EXISTS);
        }
        return punchRecord;
    }

    @Override
    public List<PunchRecordRespVO> getPunchRecords(Long userId, Integer limit) {
        // 获取打卡记录
        List<PunchRecordDO> punchRecords = punchRecordMapper.selectByUserId(userId, limit);

        if (CollUtil.isEmpty(punchRecords)) {
            return new ArrayList<>();
        }

        // 获取用户信息
        UserDO user = userMapper.selectById(userId);

        // 获取打卡绘本关联
        List<Long> punchIds = punchRecords.stream().map(PunchRecordDO::getId).collect(Collectors.toList());
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchIds(punchIds);
        Map<Long, List<PunchBookDO>> punchBooksMap = punchBooks.stream()
                .collect(Collectors.groupingBy(PunchBookDO::getPunchId));

        // 获取绘本信息
        List<Long> bookIds = punchBooks.stream().map(PunchBookDO::getBookId).collect(Collectors.toList());
        List<PictureBookDO> books = pictureBookMapper.selectBatchIds(bookIds);
        Map<Long, PictureBookDO> bookMap = books.stream()
                .collect(Collectors.toMap(PictureBookDO::getId, book -> book));

        // 获取绘本集信息
        List<Long> bookSetIds = books.stream().map(PictureBookDO::getBookSetId).distinct().collect(Collectors.toList());
        List<BookSetDO> bookSets = bookSetMapper.selectBatchIds(bookSetIds);
        Map<Long, BookSetDO> bookSetMap = bookSets.stream()
                .collect(Collectors.toMap(BookSetDO::getId, bookSet -> bookSet));

        // 构建响应
        return punchRecords.stream().map(punchRecord -> {
            PunchRecordRespVO respVO = new PunchRecordRespVO();
            respVO.setId(punchRecord.getId());
            respVO.setPunchDate(punchRecord.getPunchDate());
            respVO.setCommentText(punchRecord.getCommentText());
            respVO.setBookCount(punchRecord.getBookCount());
            respVO.setDayCount(punchRecord.getDayCount());
            respVO.setIsPublic(punchRecord.getIsPublic());

            // 计算当日阅读时长
            LocalDateTime dayStart = punchRecord.getPunchDate().atStartOfDay();
            LocalDateTime dayEnd = punchRecord.getPunchDate().atTime(LocalTime.MAX);
            Integer dailyReadingTime = readingRecordMapper.sumReadingTimeByUserIdAndDateRange(
                    punchRecord.getUserId(), dayStart, dayEnd);
            respVO.setReadingTime(dailyReadingTime != null ? dailyReadingTime : 0);
            respVO.setCreateTime(punchRecord.getCreateTime() != null ? punchRecord.getCreateTime().toString() : null);
            respVO.setUpdateTime(punchRecord.getUpdateTime() != null ? punchRecord.getUpdateTime().toString() : null);

            // 设置用户信息
            if (user != null) {
                PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
                userInfo.setUserId(user.getId());
                userInfo.setUsername(user.getUsername());
                userInfo.setNickname(user.getNickname());
                // 转换头像URL为完整URL
                userInfo.setAvatar(staticResourceUrlUtils.toFullUrl(user.getAvatar()));
                respVO.setUserInfo(userInfo);
            }

            // 设置关注状态（自己的记录不需要关注状态，设为false）
            respVO.setIsFollowed(false);
            
            // 设置绘本列表
            List<PunchBookDO> recordBooks = punchBooksMap.get(punchRecord.getId());
            if (CollUtil.isNotEmpty(recordBooks)) {
                List<PunchRecordRespVO.PunchBookVO> bookVOs = recordBooks.stream().map(punchBook -> {
                    PictureBookDO book = bookMap.get(punchBook.getBookId());
                    if (book == null) return null;
                    
                    BookSetDO bookSet = bookSetMap.get(book.getBookSetId());
                    
                    PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                    bookVO.setId(book.getId());
                    bookVO.setTitle(book.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(book.getCover()));
                    bookVO.setBookSetTitle(bookSet != null ? bookSet.getTitle() : null);
                    return bookVO;
                }).filter(bookVO -> bookVO != null).collect(Collectors.toList());
                
                respVO.setBooks(bookVOs);
            }
            
            return respVO;
        }).collect(Collectors.toList());
    }

    @Override
    public PageResult<PunchRecordRespVO> getPunchRecordsPage(Long userId, PageParam pageParam) {
        // 分页获取打卡记录
        PageResult<PunchRecordDO> pageResult = punchRecordMapper.selectPageByUserId(userId, pageParam);

        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        // 获取用户信息
        UserDO user = userMapper.selectById(userId);

        // 获取打卡绘本关联
        List<Long> punchIds = pageResult.getList().stream().map(PunchRecordDO::getId).collect(Collectors.toList());
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchIds(punchIds);
        Map<Long, List<PunchBookDO>> punchBooksMap = punchBooks.stream()
                .collect(Collectors.groupingBy(PunchBookDO::getPunchId));

        // 获取绘本信息
        List<Long> bookIds = punchBooks.stream().map(PunchBookDO::getBookId).collect(Collectors.toList());
        List<PictureBookDO> books = pictureBookMapper.selectBatchIds(bookIds);
        Map<Long, PictureBookDO> bookMap = books.stream()
                .collect(Collectors.toMap(PictureBookDO::getId, book -> book));

        // 获取绘本集信息
        List<Long> bookSetIds = books.stream().map(PictureBookDO::getBookSetId).collect(Collectors.toList());
        List<BookSetDO> bookSets = bookSetMapper.selectBatchIds(bookSetIds);
        Map<Long, BookSetDO> bookSetMap = bookSets.stream()
                .collect(Collectors.toMap(BookSetDO::getId, bookSet -> bookSet));

        // 转换为响应对象
        List<PunchRecordRespVO> respVOList = pageResult.getList().stream().map(punchRecord -> {
            PunchRecordRespVO respVO = new PunchRecordRespVO();
            respVO.setId(punchRecord.getId());
            respVO.setPunchDate(punchRecord.getPunchDate());
            respVO.setCommentText(punchRecord.getCommentText());
            respVO.setBookCount(punchRecord.getBookCount());
            respVO.setCreateTime(punchRecord.getCreateTime().toString());
            respVO.setIsPublic(punchRecord.getIsPublic());

            // 设置用户信息
            if (user != null) {
                PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
                userInfo.setUserId(user.getId());
                userInfo.setUsername(user.getUsername());
                userInfo.setNickname(user.getNickname());
                userInfo.setAvatar(staticResourceUrlUtils.toFullUrl(user.getAvatar()));
                respVO.setUserInfo(userInfo);
            }

            // 设置关注状态（自己的记录不需要关注状态，设为false）
            respVO.setIsFollowed(false);

            // 设置绘本列表
            List<PunchBookDO> recordBooks = punchBooksMap.get(punchRecord.getId());
            if (CollUtil.isNotEmpty(recordBooks)) {
                List<PunchRecordRespVO.PunchBookVO> bookVOs = recordBooks.stream().map(punchBook -> {
                    PictureBookDO book = bookMap.get(punchBook.getBookId());
                    if (book == null) return null;

                    BookSetDO bookSet = bookSetMap.get(book.getBookSetId());

                    PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                    bookVO.setId(book.getId());
                    bookVO.setTitle(book.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(book.getCover()));
                    bookVO.setBookSetTitle(bookSet != null ? bookSet.getTitle() : null);
                    return bookVO;
                }).filter(bookVO -> bookVO != null).collect(Collectors.toList());

                respVO.setBooks(bookVOs);
            }

            return respVO;
        }).collect(Collectors.toList());

        return new PageResult<>(respVOList, pageResult.getTotal());
    }

    @Override
    public PunchCalendarRespVO getPunchCalendar(Long userId, String yearMonth) {
        // 解析年月
        YearMonth ym = YearMonth.parse(yearMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        LocalDate startDate = ym.atDay(1);
        LocalDate endDate = ym.atEndOfMonth();
        
        // 获取该月的打卡记录
        List<PunchRecordDO> monthPunches = punchRecordMapper.selectByUserIdAndDateRange(userId, startDate, endDate);
        Map<LocalDate, PunchRecordDO> punchMap = monthPunches.stream()
                .collect(Collectors.toMap(PunchRecordDO::getPunchDate, punch -> punch));
        
        // 构建日历数据
        List<PunchCalendarRespVO.PunchDayVO> punchDays = new ArrayList<>();
        LocalDate currentDate = startDate;
        while (!currentDate.isAfter(endDate)) {
            PunchRecordDO punch = punchMap.get(currentDate);
            
            PunchCalendarRespVO.PunchDayVO dayVO = new PunchCalendarRespVO.PunchDayVO();
            dayVO.setDate(currentDate);
            dayVO.setIsPunched(punch != null);
            dayVO.setBookCount(punch != null ? punch.getBookCount() : 0);
            
            punchDays.add(dayVO);
            currentDate = currentDate.plusDays(1);
        }
        
        // 获取统计数据
        UserReadingStatsDO stats = userReadingStatsMapper.selectByUserId(userId);
        PunchCalendarRespVO.PunchStatsVO statsVO = new PunchCalendarRespVO.PunchStatsVO();
        if (stats != null) {
            statsVO.setMonthPunchDays(monthPunches.size());
            statsVO.setTotalPunchDays(stats.getTotalPunchDays());
            statsVO.setCurrentStreak(stats.getCurrentStreak());
            statsVO.setMaxStreak(stats.getMaxStreak());
        } else {
            statsVO.setMonthPunchDays(monthPunches.size());
            statsVO.setTotalPunchDays(0);
            statsVO.setCurrentStreak(0);
            statsVO.setMaxStreak(0);
        }
        
        // 构建响应
        PunchCalendarRespVO result = new PunchCalendarRespVO();
        result.setYearMonth(yearMonth);
        result.setPunchDays(punchDays);
        result.setStats(statsVO);
        
        return result;
    }

    @Override
    public boolean canPunchToday(Long userId) {
        LocalDate today = LocalDate.now();
        
        // 检查今日是否已打卡
        PunchRecordDO todayPunch = punchRecordMapper.selectByUserIdAndDate(userId, today);
        if (todayPunch != null) {
            return false;
        }
        
        // 检查今日是否有阅读记录
        LocalDateTime todayStart = today.atStartOfDay();
        LocalDateTime todayEnd = today.atTime(LocalTime.MAX);
        Long todayReadCount = readingRecordMapper.countTodayReadBooks(userId, todayStart, todayEnd);
        
        return todayReadCount > 0;
    }

    @Override
    public PunchRecordRespVO getTodayPunch(Long userId) {
        LocalDate today = LocalDate.now();
        PunchRecordDO todayPunch = punchRecordMapper.selectByUserIdAndDate(userId, today);
        
        if (todayPunch == null) {
            return null;
        }
        
        List<PunchRecordRespVO> punchList = getPunchRecords(userId, 1);
        return CollUtil.isNotEmpty(punchList) ? punchList.get(0) : null;
    }

    @Override
    public PunchRecordRespVO getPunchDetail(Long userId, Long punchId) {
        PunchRecordDO punchRecord = getPunchRecord(punchId);

        // 验证权限：只有打卡记录的所有者或公开的打卡记录才能查看
        if (!punchRecord.getUserId().equals(userId) && !punchRecord.getIsPublic()) {
            throw exception(PUNCH_RECORD_NOT_EXISTS);
        }

        // 转换为响应VO
        PunchRecordRespVO respVO = BeanUtils.toBean(punchRecord, PunchRecordRespVO.class);

        // 计算当日阅读时长
        LocalDateTime dayStart = punchRecord.getPunchDate().atStartOfDay();
        LocalDateTime dayEnd = punchRecord.getPunchDate().atTime(LocalTime.MAX);
        Integer dailyReadingTime = readingRecordMapper.sumReadingTimeByUserIdAndDateRange(
                punchRecord.getUserId(), dayStart, dayEnd);
        respVO.setReadingTime(dailyReadingTime != null ? dailyReadingTime : 0);

        // 设置绘本列表
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchId(punchId);
        if (CollUtil.isNotEmpty(punchBooks)) {
            List<PunchRecordRespVO.PunchBookVO> bookVOs = punchBooks.stream().map(punchBook -> {
                PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                bookVO.setId(punchBook.getBookId());

                // 通过bookId查询绘本详细信息
                PictureBookDO pictureBook = pictureBookMapper.selectById(punchBook.getBookId());
                if (pictureBook != null) {
                    bookVO.setTitle(pictureBook.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(pictureBook.getCover()));
                    bookVO.setBookSetId(pictureBook.getBookSetId());

                    // 通过bookSetId查询绘本集信息
                    BookSetDO bookSet = bookSetMapper.selectById(pictureBook.getBookSetId());
                    if (bookSet != null) {
                        bookVO.setBookSetTitle(bookSet.getTitle());
                    }
                }

                return bookVO;
            }).collect(Collectors.toList());
            respVO.setBooks(bookVOs);
        }

        // 设置用户信息
        if (!punchRecord.getUserId().equals(userId)) {
            // 如果是查看其他用户的打卡，需要设置用户信息
            PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
            userInfo.setUserId(punchRecord.getUserId());
            // 这里可以调用用户服务获取用户信息，暂时设置基本信息
            userInfo.setNickname("用户" + punchRecord.getUserId());
            respVO.setUserInfo(userInfo);
        }

        return respVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePunch(Long userId, Long punchId) {
        PunchRecordDO punchRecord = getPunchRecord(punchId);
        
        // 验证权限
        if (!punchRecord.getUserId().equals(userId)) {
            throw exception(PUNCH_RECORD_NOT_EXISTS);
        }
        
        // 删除打卡绘本关联
        punchBookMapper.deleteByPunchId(punchId);
        
        // 删除打卡记录
        punchRecordMapper.deleteById(punchId);
        
        // 更新用户统计数据
        updateUserStats(userId);
    }

    @Override
    public void updateUserStats(Long userId) {
        // 获取用户所有打卡记录
        List<PunchRecordDO> allPunches = punchRecordMapper.selectByUserId(userId, null);
        
        if (CollUtil.isEmpty(allPunches)) {
            // 如果没有打卡记录，删除统计数据
            userReadingStatsMapper.deleteByUserId(userId);
            return;
        }
        
        // 计算统计数据
        int totalPunchDays = allPunches.size();
        int totalReadBooks = allPunches.stream().mapToInt(PunchRecordDO::getBookCount).sum();
        
        // 计算连续打卡天数
        int currentStreak = calculateCurrentStreak(allPunches);
        int maxStreak = calculateMaxStreak(allPunches);
        
        LocalDate lastPunchDate = allPunches.stream()
                .map(PunchRecordDO::getPunchDate)
                .max(LocalDate::compareTo)
                .orElse(null);
        
        // 更新或创建统计记录
        UserReadingStatsDO existingStats = userReadingStatsMapper.selectByUserId(userId);
        if (existingStats == null) {
            UserReadingStatsDO newStats = UserReadingStatsDO.builder()
                    .userId(userId)
                    .totalPunchDays(totalPunchDays)
                    .totalReadBooks(totalReadBooks)
                    .currentStreak(currentStreak)
                    .maxStreak(maxStreak)
                    .lastPunchDate(lastPunchDate)
                    .build();
            userReadingStatsMapper.insert(newStats);
        } else {
            UserReadingStatsDO updateStats = UserReadingStatsDO.builder()
                    .id(existingStats.getId())
                    .totalPunchDays(totalPunchDays)
                    .totalReadBooks(totalReadBooks)
                    .currentStreak(currentStreak)
                    .maxStreak(maxStreak)
                    .lastPunchDate(lastPunchDate)
                    .build();
            userReadingStatsMapper.updateById(updateStats);
        }
    }

    /**
     * 计算当前连续打卡天数
     */
    private int calculateCurrentStreak(List<PunchRecordDO> punches) {
        if (CollUtil.isEmpty(punches)) {
            return 0;
        }
        
        // 按日期倒序排列
        List<LocalDate> dates = punches.stream()
                .map(PunchRecordDO::getPunchDate)
                .sorted((d1, d2) -> d2.compareTo(d1))
                .collect(Collectors.toList());
        
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.minusDays(1);
        
        // 如果今天或昨天没有打卡，连续天数为0
        if (!dates.contains(today) && !dates.contains(yesterday)) {
            return 0;
        }
        
        int streak = 0;
        LocalDate expectedDate = dates.contains(today) ? today : yesterday;
        
        for (LocalDate date : dates) {
            if (date.equals(expectedDate)) {
                streak++;
                expectedDate = expectedDate.minusDays(1);
            } else {
                break;
            }
        }
        
        return streak;
    }

    /**
     * 计算最长连续打卡天数
     */
    private int calculateMaxStreak(List<PunchRecordDO> punches) {
        if (CollUtil.isEmpty(punches)) {
            return 0;
        }
        
        // 按日期正序排列
        List<LocalDate> dates = punches.stream()
                .map(PunchRecordDO::getPunchDate)
                .sorted()
                .collect(Collectors.toList());
        
        int maxStreak = 1;
        int currentStreak = 1;
        
        for (int i = 1; i < dates.size(); i++) {
            LocalDate prevDate = dates.get(i - 1);
            LocalDate currentDate = dates.get(i);
            
            if (currentDate.equals(prevDate.plusDays(1))) {
                currentStreak++;
                maxStreak = Math.max(maxStreak, currentStreak);
            } else {
                currentStreak = 1;
            }
        }
        
        return maxStreak;
    }

    @Override
    public UserStatsRespVO getUserStats(Long userId) {
        // 获取用户统计数据
        UserReadingStatsDO stats = userReadingStatsMapper.selectByUserId(userId);

        // 获取本月打卡天数
        LocalDate now = LocalDate.now();
        LocalDate monthStart = now.withDayOfMonth(1);
        List<PunchRecordDO> thisMonthPunches = punchRecordMapper.selectByUserIdAndDateRange(userId, monthStart, now);

        // 计算总阅读时长和绘本数
        Integer totalReadingTime = readingRecordMapper.sumReadingTimeByUserId(userId);
        Integer totalBooksRead = readingRecordMapper.countDistinctBooksByUserId(userId);

        // 计算平均阅读时长
        Integer averageReadingTime = 0;
        if (stats != null && stats.getTotalPunchDays() > 0 && totalReadingTime != null) {
            averageReadingTime = totalReadingTime / stats.getTotalPunchDays();
        }

        // 计算打卡率
        Double punchRate = 0.0;
        if (stats != null && stats.getTotalPunchDays() > 0) {
            // 假设用户注册后应该每天打卡，计算实际打卡率
            long daysSinceFirstPunch = java.time.temporal.ChronoUnit.DAYS.between(
                    stats.getCreateTime().toLocalDate(), LocalDate.now()) + 1;
            punchRate = (double) stats.getTotalPunchDays() / daysSinceFirstPunch;
        }

        // 获取月度和周统计（简化实现）
        List<UserStatsRespVO.MonthlyStatsVO> monthlyStats = getMonthlyStatsForUser(userId);
        List<UserStatsRespVO.WeeklyStatsVO> weeklyStats = getWeeklyStatsForUser(userId);

        return PunchConvert.INSTANCE.convertToUserStatsRespVO(
                stats != null ? stats.getTotalPunchDays() : 0,
                stats != null ? stats.getCurrentStreak() : 0,
                stats != null ? stats.getMaxStreak() : 0,
                thisMonthPunches.size(),
                totalReadingTime,
                totalBooksRead,
                averageReadingTime,
                punchRate,
                monthlyStats,
                weeklyStats
        );
    }

    @Override
    public PunchCalendarAllRespVO getPunchCalendarAll(Long userId) {
        // 获取用户所有打卡记录
        List<PunchRecordDO> allPunches = punchRecordMapper.selectByUserId(userId, null);

        // 获取统计数据
        UserReadingStatsDO stats = userReadingStatsMapper.selectByUserId(userId);

        // 获取本月打卡天数
        LocalDate now = LocalDate.now();
        LocalDate monthStart = now.withDayOfMonth(1);
        List<PunchRecordDO> thisMonthPunches = punchRecordMapper.selectByUserIdAndDateRange(userId, monthStart, now);

        // 转换为日历数据
        Map<String, PunchCalendarAllRespVO.PunchDayInfo> calendarData =
                PunchConvert.INSTANCE.convertToCalendarData(allPunches);

        // 获取最近打卡记录
        List<PunchRecordRespVO> recentPunches = getPunchRecords(userId, 10);

        return PunchConvert.INSTANCE.convertToPunchCalendarAllRespVO(
                stats != null ? stats.getTotalPunchDays() : 0,
                stats != null ? stats.getCurrentStreak() : 0,
                thisMonthPunches.size(),
                calendarData,
                recentPunches
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePunch(Long userId, Long punchId, PunchUpdateReqVO updateReqVO) {
        // 校验打卡记录存在且属于当前用户
        PunchRecordDO punchRecord = getPunchRecord(punchId);
        if (!punchRecord.getUserId().equals(userId)) {
            throw exception(PUNCH_RECORD_NOT_EXISTS);
        }

        // 更新打卡记录
        PunchRecordDO updateObj = PunchRecordDO.builder()
                .id(punchId)
                .commentText(updateReqVO.getCommentText())
                .isPublic(updateReqVO.getIsPublic())
                .build();
        punchRecordMapper.updateById(updateObj);

        // 如果更新了绘本列表，需要更新关联关系
        if (updateReqVO.getBookIds() != null) {
            // 删除原有关联
            punchBookMapper.deleteByPunchId(punchId);

            // 创建新关联
            for (Long bookId : updateReqVO.getBookIds()) {
                PunchBookDO punchBook = PunchBookDO.builder()
                        .punchId(punchId)
                        .bookId(bookId)
                        .build();
                punchBookMapper.insert(punchBook);
            }

            // 更新绘本数量
            updateObj.setBookCount(updateReqVO.getBookIds().size());
            punchRecordMapper.updateById(updateObj);
        }

        log.info("更新打卡记录成功，用户ID: {}, 打卡记录ID: {}", userId, punchId);
    }

    /**
     * 获取用户月度统计数据
     */
    private List<UserStatsRespVO.MonthlyStatsVO> getMonthlyStatsForUser(Long userId) {
        List<UserStatsRespVO.MonthlyStatsVO> monthlyStats = new ArrayList<>();

        // 获取最近6个月的数据
        LocalDate now = LocalDate.now();
        for (int i = 0; i < 6; i++) {
            LocalDate monthStart = now.minusMonths(i).withDayOfMonth(1);
            LocalDate monthEnd = monthStart.plusMonths(1).minusDays(1);

            // 获取该月的打卡记录
            List<PunchRecordDO> monthPunches = punchRecordMapper.selectByUserIdAndDateRange(userId, monthStart, monthEnd);

            // 获取该月的阅读记录
            LocalDateTime monthStartTime = monthStart.atStartOfDay();
            LocalDateTime monthEndTime = monthEnd.atTime(LocalTime.MAX);
            List<ReadingRecordDO> monthReadings = readingRecordMapper.selectByUserIdAndDateRange(userId, monthStartTime, monthEndTime);

            // 计算统计数据
            int punchDays = monthPunches.size();
            int bookCount = monthReadings.stream()
                    .collect(Collectors.groupingBy(ReadingRecordDO::getBookId))
                    .size(); // 去重计算绘本数量
            int readingTime = monthReadings.stream()
                    .mapToInt(record -> record.getReadingTime() != null ? record.getReadingTime() : 0)
                    .sum();

            UserStatsRespVO.MonthlyStatsVO monthlyVO = new UserStatsRespVO.MonthlyStatsVO();
            monthlyVO.setMonth(monthStart.format(DateTimeFormatter.ofPattern("yyyy-MM")));
            monthlyVO.setPunchDays(punchDays);
            monthlyVO.setBookCount(bookCount);
            monthlyVO.setReadingTime(readingTime / 60); // 转换为分钟

            monthlyStats.add(monthlyVO);
        }

        // 按月份倒序排列（最新的在前）
        monthlyStats.sort((a, b) -> b.getMonth().compareTo(a.getMonth()));

        return monthlyStats;
    }

    /**
     * 获取用户周统计数据
     */
    private List<UserStatsRespVO.WeeklyStatsVO> getWeeklyStatsForUser(Long userId) {
        List<UserStatsRespVO.WeeklyStatsVO> weeklyStats = new ArrayList<>();

        // 获取最近8周的数据
        LocalDate now = LocalDate.now();
        for (int i = 0; i < 8; i++) {
            // 计算周的开始和结束日期（周一到周日）
            LocalDate weekStart = now.minusWeeks(i).with(DayOfWeek.MONDAY);
            LocalDate weekEnd = weekStart.plusDays(6);

            // 获取该周的打卡记录
            List<PunchRecordDO> weekPunches = punchRecordMapper.selectByUserIdAndDateRange(userId, weekStart, weekEnd);

            // 获取该周的阅读记录
            LocalDateTime weekStartTime = weekStart.atStartOfDay();
            LocalDateTime weekEndTime = weekEnd.atTime(LocalTime.MAX);
            List<ReadingRecordDO> weekReadings = readingRecordMapper.selectByUserIdAndDateRange(userId, weekStartTime, weekEndTime);

            // 计算统计数据
            int punchDays = weekPunches.size();
            int bookCount = weekReadings.stream()
                    .collect(Collectors.groupingBy(ReadingRecordDO::getBookId))
                    .size(); // 去重计算绘本数量
            int readingTime = weekReadings.stream()
                    .mapToInt(record -> record.getReadingTime() != null ? record.getReadingTime() : 0)
                    .sum();

            UserStatsRespVO.WeeklyStatsVO weeklyVO = new UserStatsRespVO.WeeklyStatsVO();
            // 使用ISO周格式，例如：2024-W01
            int year = weekStart.getYear();
            int weekOfYear = weekStart.get(WeekFields.ISO.weekOfYear());
            weeklyVO.setWeek(String.format("%d-W%02d", year, weekOfYear));
            weeklyVO.setPunchDays(punchDays);
            weeklyVO.setBookCount(bookCount);
            weeklyVO.setReadingTime(readingTime / 60); // 转换为分钟

            weeklyStats.add(weeklyVO);
        }

        // 按周倒序排列（最新的在前）
        weeklyStats.sort((a, b) -> b.getWeek().compareTo(a.getWeek()));

        return weeklyStats;
    }

    @Override
    public List<PunchRecordRespVO> getPublicPunchRecords(Long currentUserId, Integer limit) {
        // 获取所有公开的打卡记录，按时间倒序
        List<PunchRecordDO> punchRecords = punchRecordMapper.selectPublicPunchRecords(limit);

        if (CollUtil.isEmpty(punchRecords)) {
            return new ArrayList<>();
        }

        // 获取所有用户ID
        List<Long> userIds = punchRecords.stream()
                .map(PunchRecordDO::getUserId)
                .distinct()
                .collect(Collectors.toList());

        // 批量获取用户信息
        List<UserDO> users = userMapper.selectBatchIds(userIds);
        Map<Long, UserDO> userMap = users.stream()
                .collect(Collectors.toMap(UserDO::getId, user -> user));

        // 批量检查关注状态
        Map<Long, Boolean> followStatusMap;
        if (currentUserId != null) {
            followStatusMap = followService.batchCheckFollowStatus(currentUserId, userIds);
        } else {
            followStatusMap = Collections.emptyMap();
        }

        // 获取打卡绘本关联
        List<Long> punchIds = punchRecords.stream().map(PunchRecordDO::getId).collect(Collectors.toList());
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchIds(punchIds);
        Map<Long, List<PunchBookDO>> punchBooksMap = punchBooks.stream()
                .collect(Collectors.groupingBy(PunchBookDO::getPunchId));

        // 获取绘本信息
        List<Long> bookIds = punchBooks.stream().map(PunchBookDO::getBookId).collect(Collectors.toList());
        List<PictureBookDO> books = pictureBookMapper.selectBatchIds(bookIds);
        Map<Long, PictureBookDO> bookMap = books.stream()
                .collect(Collectors.toMap(PictureBookDO::getId, book -> book));

        // 获取绘本集信息
        List<Long> bookSetIds = books.stream().map(PictureBookDO::getBookSetId).distinct().collect(Collectors.toList());
        List<BookSetDO> bookSets = bookSetMapper.selectBatchIds(bookSetIds);
        Map<Long, BookSetDO> bookSetMap = bookSets.stream()
                .collect(Collectors.toMap(BookSetDO::getId, bookSet -> bookSet));

        // 构建响应
        return punchRecords.stream().map(punchRecord -> {
            PunchRecordRespVO respVO = new PunchRecordRespVO();
            respVO.setId(punchRecord.getId());
            respVO.setPunchDate(punchRecord.getPunchDate());
            respVO.setCommentText(punchRecord.getCommentText());
            respVO.setBookCount(punchRecord.getBookCount());
            respVO.setDayCount(punchRecord.getDayCount());
            respVO.setIsPublic(punchRecord.getIsPublic());
            respVO.setCreateTime(punchRecord.getCreateTime() != null ? punchRecord.getCreateTime().toString() : null);
            respVO.setUpdateTime(punchRecord.getUpdateTime() != null ? punchRecord.getUpdateTime().toString() : null);

            // 设置用户信息
            UserDO user = userMap.get(punchRecord.getUserId());
            if (user != null) {
                PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
                userInfo.setUserId(user.getId());
                userInfo.setUsername(user.getUsername());
                userInfo.setNickname(user.getNickname());
                // 转换头像URL为完整URL
                userInfo.setAvatar(staticResourceUrlUtils.toFullUrl(user.getAvatar()));
                respVO.setUserInfo(userInfo);
            }

            // 设置关注状态
            respVO.setIsFollowed(followStatusMap.getOrDefault(punchRecord.getUserId(), false));

            // 设置绘本列表
            List<PunchBookDO> recordBooks = punchBooksMap.get(punchRecord.getId());
            if (CollUtil.isNotEmpty(recordBooks)) {
                List<PunchRecordRespVO.PunchBookVO> bookVOs = recordBooks.stream().map(punchBook -> {
                    PictureBookDO book = bookMap.get(punchBook.getBookId());
                    if (book == null) return null;

                    BookSetDO bookSet = bookSetMap.get(book.getBookSetId());

                    PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                    bookVO.setId(book.getId());
                    bookVO.setTitle(book.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(book.getCover()));
                    bookVO.setBookSetTitle(bookSet != null ? bookSet.getTitle() : null);
                    return bookVO;
                }).filter(bookVO -> bookVO != null).collect(Collectors.toList());

                respVO.setBooks(bookVOs);
            }

            return respVO;
        }).collect(Collectors.toList());
    }

    @Override
    public PageResult<PunchRecordRespVO> getPublicPunchRecordsPage(Long currentUserId, PageParam pageParam) {
        // 分页获取所有公开的打卡记录，按时间倒序
        PageResult<PunchRecordDO> pageResult = punchRecordMapper.selectPublicPunchRecordsPage(pageParam);

        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        // 获取所有用户ID
        List<Long> userIds = pageResult.getList().stream()
                .map(PunchRecordDO::getUserId)
                .distinct()
                .collect(Collectors.toList());

        // 批量获取用户信息
        List<UserDO> users = userMapper.selectBatchIds(userIds);
        Map<Long, UserDO> userMap = users.stream()
                .collect(Collectors.toMap(UserDO::getId, user -> user));

        // 批量检查关注状态
        Map<Long, Boolean> followStatusMap;
        if (currentUserId != null) {
            followStatusMap = followService.batchCheckFollowStatus(currentUserId, userIds);
        } else {
            followStatusMap = Collections.emptyMap();
        }

        // 获取打卡绘本关联
        List<Long> punchIds = pageResult.getList().stream().map(PunchRecordDO::getId).collect(Collectors.toList());
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchIds(punchIds);
        Map<Long, List<PunchBookDO>> punchBooksMap = punchBooks.stream()
                .collect(Collectors.groupingBy(PunchBookDO::getPunchId));

        // 获取绘本信息
        List<Long> bookIds = punchBooks.stream().map(PunchBookDO::getBookId).collect(Collectors.toList());
        List<PictureBookDO> books = pictureBookMapper.selectBatchIds(bookIds);
        Map<Long, PictureBookDO> bookMap = books.stream()
                .collect(Collectors.toMap(PictureBookDO::getId, book -> book));

        // 获取绘本集信息
        List<Long> bookSetIds = books.stream().map(PictureBookDO::getBookSetId).collect(Collectors.toList());
        List<BookSetDO> bookSets = bookSetMapper.selectBatchIds(bookSetIds);
        Map<Long, BookSetDO> bookSetMap = bookSets.stream()
                .collect(Collectors.toMap(BookSetDO::getId, bookSet -> bookSet));

        // 转换为响应对象
        List<PunchRecordRespVO> respVOList = pageResult.getList().stream().map(punchRecord -> {
            PunchRecordRespVO respVO = new PunchRecordRespVO();
            respVO.setId(punchRecord.getId());
            respVO.setPunchDate(punchRecord.getPunchDate());
            respVO.setCommentText(punchRecord.getCommentText());
            respVO.setBookCount(punchRecord.getBookCount());
            respVO.setCreateTime(punchRecord.getCreateTime().toString());
            respVO.setIsPublic(punchRecord.getIsPublic());

            // 设置用户信息
            UserDO user = userMap.get(punchRecord.getUserId());
            if (user != null) {
                PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
                userInfo.setUserId(user.getId());
                userInfo.setUsername(user.getUsername());
                userInfo.setNickname(user.getNickname());
                userInfo.setAvatar(staticResourceUrlUtils.toFullUrl(user.getAvatar()));
                respVO.setUserInfo(userInfo);
            }

            // 设置关注状态
            respVO.setIsFollowed(followStatusMap.getOrDefault(punchRecord.getUserId(), false));

            // 设置绘本列表
            List<PunchBookDO> recordBooks = punchBooksMap.get(punchRecord.getId());
            if (CollUtil.isNotEmpty(recordBooks)) {
                List<PunchRecordRespVO.PunchBookVO> bookVOs = recordBooks.stream().map(punchBook -> {
                    PictureBookDO book = bookMap.get(punchBook.getBookId());
                    if (book == null) return null;

                    BookSetDO bookSet = bookSetMap.get(book.getBookSetId());

                    PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                    bookVO.setId(book.getId());
                    bookVO.setTitle(book.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(book.getCover()));
                    bookVO.setBookSetTitle(bookSet != null ? bookSet.getTitle() : null);
                    return bookVO;
                }).filter(bookVO -> bookVO != null).collect(Collectors.toList());

                respVO.setBooks(bookVOs);
            }

            return respVO;
        }).collect(Collectors.toList());

        return new PageResult<>(respVOList, pageResult.getTotal());
    }

    @Override
    public List<PunchRecordRespVO> getPunchRecordsByClassId(Long classId) {
        // 获取班级打卡记录
        List<PunchRecordDO> punchRecords = punchRecordMapper.selectByClassId(classId);

        if (CollUtil.isEmpty(punchRecords)) {
            return new ArrayList<>();
        }

        return convertToPunchRecordRespVOList(punchRecords);
    }

    @Override
    public List<PunchRecordRespVO> getPunchRecordsByClassIdAndDateRange(Long classId, LocalDate startDate, LocalDate endDate) {
        // 获取班级指定日期范围的打卡记录
        List<PunchRecordDO> punchRecords = punchRecordMapper.selectByClassIdAndDateRange(classId, startDate, endDate);

        if (CollUtil.isEmpty(punchRecords)) {
            return new ArrayList<>();
        }

        return convertToPunchRecordRespVOList(punchRecords);
    }

    @Override
    public Long countPunchRecordsByClassId(Long classId) {
        return punchRecordMapper.countByClassId(classId);
    }

    /**
     * 将PunchRecordDO列表转换为PunchRecordRespVO列表
     *
     * @param punchRecords 打卡记录DO列表
     * @return 打卡记录响应VO列表
     */
    private List<PunchRecordRespVO> convertToPunchRecordRespVOList(List<PunchRecordDO> punchRecords) {
        // 获取所有用户ID
        List<Long> userIds = punchRecords.stream()
                .map(PunchRecordDO::getUserId)
                .distinct()
                .collect(Collectors.toList());

        // 批量获取用户信息
        List<UserDO> users = userMapper.selectBatchIds(userIds);
        Map<Long, UserDO> userMap = users.stream()
                .collect(Collectors.toMap(UserDO::getId, user -> user));

        // 获取打卡绘本关联
        List<Long> punchIds = punchRecords.stream().map(PunchRecordDO::getId).collect(Collectors.toList());
        List<PunchBookDO> punchBooks = punchBookMapper.selectByPunchIds(punchIds);
        Map<Long, List<PunchBookDO>> punchBooksMap = punchBooks.stream()
                .collect(Collectors.groupingBy(PunchBookDO::getPunchId));

        // 获取绘本信息
        List<Long> bookIds = punchBooks.stream().map(PunchBookDO::getBookId).distinct().collect(Collectors.toList());
        List<PictureBookDO> books = pictureBookMapper.selectBatchIds(bookIds);
        Map<Long, PictureBookDO> bookMap = books.stream()
                .collect(Collectors.toMap(PictureBookDO::getId, book -> book));

        // 获取绘本集信息
        List<Long> bookSetIds = books.stream().map(PictureBookDO::getBookSetId).distinct().collect(Collectors.toList());
        List<BookSetDO> bookSets = bookSetMapper.selectBatchIds(bookSetIds);
        Map<Long, BookSetDO> bookSetMap = bookSets.stream()
                .collect(Collectors.toMap(BookSetDO::getId, bookSet -> bookSet));

        // 转换为响应VO
        return punchRecords.stream().map(punchRecord -> {
            PunchRecordRespVO respVO = new PunchRecordRespVO();
            respVO.setId(punchRecord.getId());
            respVO.setPunchDate(punchRecord.getPunchDate());
            respVO.setCommentText(punchRecord.getCommentText());
            respVO.setBookCount(punchRecord.getBookCount());
            respVO.setDayCount(punchRecord.getDayCount());
            respVO.setIsPublic(punchRecord.getIsPublic());
            respVO.setCreateTime(punchRecord.getCreateTime() != null ? punchRecord.getCreateTime().toString() : null);
            respVO.setUpdateTime(punchRecord.getUpdateTime() != null ? punchRecord.getUpdateTime().toString() : null);

            // 设置用户信息
            UserDO user = userMap.get(punchRecord.getUserId());
            if (user != null) {
                PunchRecordRespVO.UserInfoVO userInfo = new PunchRecordRespVO.UserInfoVO();
                userInfo.setUserId(user.getId());
                userInfo.setUsername(user.getUsername());
                userInfo.setNickname(user.getNickname());
                // 转换头像URL为完整URL
                userInfo.setAvatar(staticResourceUrlUtils.toFullUrl(user.getAvatar()));
                respVO.setUserInfo(userInfo);
            }

            // 设置关注状态为false（班级内查询不需要关注状态）
            respVO.setIsFollowed(false);

            // 设置绘本列表
            List<PunchBookDO> recordBooks = punchBooksMap.get(punchRecord.getId());
            if (CollUtil.isNotEmpty(recordBooks)) {
                List<PunchRecordRespVO.PunchBookVO> bookVOs = recordBooks.stream().map(punchBook -> {
                    PictureBookDO book = bookMap.get(punchBook.getBookId());
                    if (book == null) return null;

                    BookSetDO bookSet = bookSetMap.get(book.getBookSetId());

                    PunchRecordRespVO.PunchBookVO bookVO = new PunchRecordRespVO.PunchBookVO();
                    bookVO.setId(book.getId());
                    bookVO.setTitle(book.getTitle());
                    bookVO.setCover(staticResourceUrlUtils.toFullUrl(book.getCover()));
                    bookVO.setBookSetTitle(bookSet != null ? bookSet.getTitle() : null);
                    return bookVO;
                }).filter(bookVO -> bookVO != null).collect(Collectors.toList());

                respVO.setBooks(bookVOs);
            }

            return respVO;
        }).collect(Collectors.toList());
    }

}
